#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <cstdlib>
#include <thread>
#include <functional>
#include <vector>
#include <sys/timerfd.h>
#include <unistd.h>
#include <string.h>
#include <mysql/mysql.h>
#include "Log.hpp"
namespace ns_mysql
{
    using namespace ns_log;
    enum class STATU
    {
        INIT_ERROR,
        INIT_SUCCESS,
        CONNECTED,
        DISCONNECTED
    };
    static const int default_init_cnt = 3;
    class MySQL
    {
    private:
        MYSQL *_my;
        STATU _statu;
        int _init_cnt;
        int _conn_cnt;

    public:
        std::string _host;
        std::string _user;
        std::string _password;
        std::string _database;
        int _port;

    public:
        bool RetryConnect()
        {
            if (_statu != STATU::INIT_SUCCESS)
                return false;
            while (mysql_real_connect(_my,
                                      _host.c_str(),
                                      _user.c_str(),
                                      _password.c_str(),
                                      _database.c_str(),
                                      _port,
                                      nullptr,
                                      0) == nullptr &&
                   _conn_cnt)
            {
                // 重连
                --_conn_cnt;
            }
            if (_conn_cnt == 0)
            {
                LOG(ERROR, "mysql连接失败,重试次数耗尽\n");
                return false;
            }
            // 这里直接使用默认的重试次数即可
            _conn_cnt = default_init_cnt;
            _statu = STATU::CONNECTED;
            return true;
        }
        void RetryInit()
        {
            while (_my == nullptr && _init_cnt)
            {
                _my = mysql_init(nullptr);
                --_init_cnt;
            }
            if (_init_cnt == 0 && _my == nullptr)
            {
                _statu = STATU::INIT_ERROR;
                abort();
            }
            _statu = STATU::INIT_SUCCESS;
        }

    public:
        MySQL(int init_cnt = default_init_cnt, int con_cnt = default_init_cnt)
            : _my(nullptr),
              _init_cnt(init_cnt),
              _conn_cnt(con_cnt)
        {
            RetryInit();
            _init_cnt = init_cnt;
        }
        bool ConnectMysql(const std::string &host,
                          const std::string &user,
                          const std::string &password,
                          const std::string &database,
                          unsigned int port,
                          const char *unix_socket = nullptr,
                          unsigned int clientflag = 0)
        {
            _host = host;
            _user = user;
            _password = password;
            _database = database;
            _port = port;
            return RetryConnect();
        }
        ~MySQL()
        {
            if(_my)
            {
                mysql_close(_my);
                _my = nullptr;
            }
        }

        STATU Statu()
        {
            return _statu;
        }

        bool Query(const std::string &sql) // 建议使用c++11的R原始字符串
        {
            int n = mysql_query(_my, sql.c_str());
            if (n != 0)
            {
                LOG(ERROR, "SQL: %s语句执行失败.失败原因:%s\n", sql.c_str(), mysql_error(_my));
                return false;
            }
            LOG(DEBUG, "SQL: %s语句执行成功\n", sql.c_str());
            return true;
        }
        bool Query(const std::string &sql, std::vector<MYSQL_ROW> *res, int *filed)
        {
            int n = mysql_query(_my, sql.c_str());
            if (n != 0)
            {
                LOG(ERROR, "SQL: %s执行失败,错误原因: %s\n", sql.c_str(), mysql_error(_my));
                return false;
            }
            MYSQL_RES *r = mysql_store_result(_my);
            if (r == nullptr)
            {
                LOG(ERROR, "保存结果集失败,失败原因: %s\n", mysql_error(_my));
                return false;
            }
            int rows = mysql_num_rows(r);
            *filed = mysql_num_fields(r);
            for (int i = 0; i < rows; i++)
            {
                MYSQL_ROW row = mysql_fetch_row(r);
                res->push_back(std::move(row));
            }
            mysql_free_result(r);
            return true;
        }

        const char *GetConnStatu()
        {
            return mysql_stat(_my);
        }
        // 选择要连接的数据库
        bool SelectDatabase(const std::string &db)
        {
            _database = db;
            return mysql_select_db(_my, _database.c_str()) == 0 ? true : false;
        }
    };
    const std::string default_check_sql = "select 1";
    const int default_time_out = 180; // 秒为单位
    // 心跳机制
    struct HeartbeatDetect
    {
        std::thread _check;
        std::string _check_sql;
        bool _isrunning;
        int _timerfd;
        int _timeout;

    public:
        HeartbeatDetect(MySQL *my, const std::string &checksql = default_check_sql, int timeout = default_time_out)
            : _check(std::bind(&HeartbeatDetect::Read, this, my)),
              _check_sql(checksql),
              _isrunning(true),
              _timeout(timeout)
        {
            _timerfd = ::timerfd_create(CLOCK_MONOTONIC, 0);
            if (_timerfd < 0)
            {
                LOG(FATAL, "创建定时器失败,失败原因: %s\n", strerror(errno));
                abort();
            }
            struct itimerspec new_settimer;
            new_settimer.it_interval.tv_sec = _timeout;
            new_settimer.it_interval.tv_nsec = 0;
            new_settimer.it_value.tv_sec = _timeout;
            new_settimer.it_value.tv_nsec = 0;
            int n = ::timerfd_settime(_timerfd, 0, &new_settimer, nullptr);
            if (n != 0)
            {
                LOG(FATAL, "设置超时间隔失败\n");
                abort();
            }
        }
        ~HeartbeatDetect()
        {
            _isrunning = false;
            _check.detach();
            // if(_check.joinable()) _check.join();
        }
        void Read(MySQL *my)
        {
            while (_isrunning)
            {
                if (my == nullptr)
                {
                    LOG(WARNING, "检测的mysql句柄为空\n");
                    break;
                }
                uint64_t val = 0;
                ssize_t n = ::read(_timerfd, &val, sizeof(val));
                if (!_isrunning)
                    break;
                if (n >= 0)
                {
                    // 如果心跳失败，就轮询检测5次后退出
                    int cnt = 5,filed = 0;
                    std::vector<MYSQL_ROW> res;
                    while (!my->Query(_check_sql,&res,&filed) && cnt--)
                    {
                        LOG(DEBUG, "mysql连接异常了,开始心跳检测,cnt = %d\n", cnt);
                        const char *ret = my->GetConnStatu();
                        if (ret == nullptr)
                        {
                            // 说明连接状态异常
                            my->RetryConnect();
                        }
                        else
                        {
                            LOG(ERROR, "未知错误\n");
                            continue;
                        }
                    }
                }
            }
            ::close(_timerfd);
        }
    };
    class MySQLTool
    {
    private:
        MySQL _mysql;
        HeartbeatDetect _check;

    public:
        MySQLTool() : _check(&_mysql)
        {
        }
        bool Connect(const std::string &host,
                     const std::string &user,
                     const std::string &password,
                     const std::string &database,
                     unsigned int port,
                     const char *unix_socket = nullptr,
                     unsigned int clientflag = 0)
        {
            return _mysql.ConnectMysql(host, user, password, database, port, unix_socket, clientflag);
        }

        STATU Statu()
        {
            return _mysql.Statu();
        }
        // 建议使用c++11的R原始字符串
        bool Query(const std::string &sql)
        {
            return _mysql.Query(sql);
        }
        bool Query(const std::string &sql, std::vector<MYSQL_ROW>* res,int *filed)
        {
            return _mysql.Query(sql, res, filed);
        }
        bool SelectDatabase(const std::string &db)
        {
            return _mysql.SelectDatabase(db);
        }
    };
}